package crawl.http;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import utils.Logger;
import utils.Util;

/**
 * a graph contains all the pages and their relations. an edge stand a
 * "<a href>" tag
 * 
 * @author huangcd huangcd.thu@gmail.com
 */
public class PageGraph
{
    private static PageGraph  instance;
    private Map<String, Page> map;

    /**
     * get the graph instance
     * 
     * @return
     */
    public static PageGraph getInstance()
    {
        if (instance == null)
            instance = new PageGraph();
        return instance;
    }

    private PageGraph()
    {
        map = Collections.synchronizedMap(new HashMap<String, Page>());
    }

    public synchronized Collection<Page> getPages()
    {
        return map.values();
    }

    public synchronized Set<String> getUrls()
    {
        return map.keySet();
    }

    public synchronized Page getPage(String url)
    {
        return map.get(url);
    }

    /**
     * add an edge to this graph, create new node if the node does not exist.
     * 
     * @param from
     *            start of the edge
     * @param to
     *            end of the edge
     */
    public synchronized void addEdge(String from, String to)
    {
        if (!map.containsKey(from))
            map.put(from, new Page(from));
        if (!map.containsKey(to))
            map.put(to, new Page(to));

        Page fromPage = map.get(from);
        Page toPage = map.get(to);
        fromPage.addOutNode(toPage);
        toPage.addInNode(fromPage);
    }

    /**
     * delete a node from this graph
     * 
     * @param node
     */
    public synchronized void delete(String node)
    {
        if (!map.containsKey(node))
            return;
        map.get(node).delete();
        map.remove(node);
    }

    public synchronized void calcPageRank(BufferedWriter log, double minDiff,
            double d) throws IOException
    {
        // Page[] pages = map.values().toArray(new Page[map.values().size()]);
        // Arrays.sort(pages, new Comparator<Page>()
        // {
        // public int compare(Page o1, Page o2)
        // {
        // return o1.getInNodeList().size() - o2.getInNodeList().size();
        // }
        // });
        // for (Page page : pages)
        // {
        // System.out.println(page.getInNodeList().size());
        // }
        Page.minDiff = minDiff;
        double initRank = 1.0 / map.size();
        Collection<Page> values = map.values();
        for (Page p : values)
            p.setCurrentPageRank(initRank);
        double maxDiff = initRank;
        Logger.logInfo("minDiff = " + minDiff + " maxDiff = " + maxDiff);
        while (maxDiff >= Page.minDiff)
        {
            double sum = 0;
            maxDiff = 0;
            for (Page p : values)
                p.setLastPageRank(p.getCurrentPageRank());
            for (Page p : values)
            {
                // calculate the new pageRank for each pages
                double currentPageRank = 0;
                for (Page in : p.getInNodeList())
                {
                    currentPageRank += in.getLastPageRank()
                            / in.getOutNodeList().size();
                }
                currentPageRank = currentPageRank * d + (1 - d);
                p.setCurrentPageRank(currentPageRank);
                sum += currentPageRank;
            }
            for (Page p : values)
            {
                p.setCurrentPageRank(p.getCurrentPageRank() / sum);
                double diff = Math.abs(p.getCurrentPageRank()
                        - p.getLastPageRank());
                maxDiff = Math.max(maxDiff, diff);
            }
            Logger.logInfo("minDiff = " + minDiff + " maxDiff = " + maxDiff);
        }

        Page[] arrays = values.toArray(new Page[0]);
        Arrays.sort(arrays, new Comparator<Page>()
        {
            public int compare(Page o1, Page o2)
            {
                return o1.getDocumentID() - o2.getDocumentID();
            }
        });
        PrintStream ps = new PrintStream(Util.getPageRankFile());
        for (int i = 0; i < arrays.length; i++)
        {
            ps.println(arrays[i].getDocumentID() + " "
                    + arrays[i].getCurrentPageRank());
        }
        ps.close();

        Arrays.sort(arrays);
        for (int i = 0; i < arrays.length; i++)
        {
            log.write(String.format("%-5d %-20.18f %s"
                    + Util.getLineSeparator(), i, arrays[i]
                    .getCurrentPageRank(), arrays[i].getUrl()));
        }
    }
}
